package com.ccrfid.rmc6.service.business.rollcall;

import com.ccrfid.rmc6.appconfig.exception.pojo.AppException;
import com.ccrfid.rmc6.appconfig.exception.pojo.CustomErrors;
import com.ccrfid.rmc6.entity.business.rollcall.RollcallResultEntity;
import com.ccrfid.rmc6.entity.business.rollcall.RollcallScheduleEntity;
import com.ccrfid.rmc6.entity.business.rollcall.RollcallTempEntity;
import com.ccrfid.rmc6.entity.sys.OrgEntity;
import com.ccrfid.rmc6.lbservice3.mq.MqPersonnelAlarm;
import com.ccrfid.rmc6.pojo.Constants;
import com.ccrfid.rmc6.pojo.dto.rollcall.RollcallDto;
import com.ccrfid.rmc6.pojo.dto.rollcall.RollcallResultDto;
import com.ccrfid.rmc6.pojo.dto.rollcall.RollcallResultNode;
import com.ccrfid.rmc6.pojo.dto.rollcall.RollcallTimeDto;
import com.ccrfid.rmc6.pojo.msg.MsgReloadSchedule;
import com.ccrfid.rmc6.pojo.msg.MsgScheduleRollcall;
import com.ccrfid.rmc6.repository.business.rollcall.RollcallResultRepository;
import com.ccrfid.rmc6.repository.business.rollcall.RollcallScheduleRepository;
import com.ccrfid.rmc6.repository.business.rollcall.RollcallTempRepository;
import com.ccrfid.rmc6.repository.sys.OrgRepository;
import com.ccrfid.rmc6.service.amq.MqMsgProducer;
import com.ccrfid.rmc6.service.sys.OrgService;
import com.ccrfid.rmc6.util.DateUtil;
import com.ccrfid.rmc6.util.JsonUtil;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.Transformers;
import org.hibernate.type.IntegerType;
import org.hibernate.type.LongType;
import org.hibernate.type.StringType;
import org.hibernate.type.TimestampType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.ws.rs.core.Response;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ScheduledFuture;

@Service
@Transactional
public class RollcallResultServiceImpl implements RollcallResultService {
    private static final Logger logger = LoggerFactory.getLogger(RollcallResultServiceImpl.class);
    @Autowired
    private TaskScheduler taskScheduler;
    @PersistenceContext
    private EntityManager entityManager;
    @Autowired
    private EventBus eventBus;
    @Autowired
    private RollcallScheduleRepository rollcallScheduleRepository;
    @Autowired
    private RollcallResultRepository rollcallResultRepository;
    @Autowired
    private RollcallTempRepository rollcallTempRepository;
    @Autowired
    private OrgRepository orgRepository;
    @Autowired
    private OrgService orgService;
    @Autowired
    private MqMsgProducer mqMsgProducer;

    @Value("${rollcall.timeout}")
    private Integer rollcallTimeout;

    private List<ScheduledFuture> futureList = new ArrayList<>();

    @PostConstruct
    private void init() {
        //
        eventBus.register(this);
        //
        initScheduleTask();
    }

    private void initScheduleTask() {
        Date now = new Date();
        java.sql.Date today = new java.sql.Date(now.getTime());
        List<RollcallScheduleEntity> scheduleList = rollcallScheduleRepository.getAllBy();
        for (RollcallScheduleEntity scheduleEntity : scheduleList) {
            if (scheduleEntity.getEnabled().equals(1)) {
                Date startTime = DateUtil.mergeDateAndTime(today, scheduleEntity.getScheduleTime());
                if (startTime.before(now)) {
                    startTime = DateUtil.getOneDayLater(startTime);
                }
                //开启定时任务
                ScheduledFuture future = taskScheduler.scheduleAtFixedRate(new RollcallTask(scheduleEntity.getScheduleTime()), startTime, 24 * 60 * 60 * 1000);
                futureList.add(future);
                logger.debug("add ScheduledFuture {}", future.hashCode());
            }
        }
    }

    private void cancelScheduleTask() {
        Iterator<ScheduledFuture> iterator = futureList.iterator();
        while (iterator.hasNext()) {
            ScheduledFuture future = iterator.next();
            if (future.cancel(true)) {
                iterator.remove();
                logger.debug("cancel ScheduledFuture return true, {}", future.hashCode());
            } else {
                logger.debug("cancel ScheduledFuture return false, {}", future.hashCode());
            }
        }
    }

    @Subscribe
    private void reloadScheduleService(MsgReloadSchedule msgReloadSchedule) {
        logger.debug("reloadScheduleService ...");
        //
        cancelScheduleTask();
        //
        initScheduleTask();
    }

    private class RollcallTask implements Runnable {
        private Time scheduleTime;

        public RollcallTask(Time scheduleTime) {
            this.scheduleTime = scheduleTime;
        }

        @Override
        public void run() {
            Date now = new Date();
            java.sql.Date today = new java.sql.Date(now.getTime());
            Date rollcallTime = DateUtil.mergeDateAndTime(today, scheduleTime);
            //自动点名
            mqMsgProducer.send2Topic("ws_schedule_rollcall",
                    JsonUtil.INSTANCE.writeValueAsString(new MsgScheduleRollcall(rollcallTime)));
        }
    }

    //自动点名消息
    @JmsListener(destination = "ws_schedule_rollcall", containerFactory = "topicListenerCF")
    public void onMqMsgScheduleRollcall(String content) {
        logger.debug("recv from topic[ws_schedule_rollcall]: {}", content);
        MsgScheduleRollcall msgScheduleRollcall = JsonUtil.INSTANCE.readValue(content, MsgScheduleRollcall.class);
        eventBus.post(msgScheduleRollcall);
    }

    @Subscribe
    public void onMsgScheduleRollcall(MsgScheduleRollcall msgScheduleRollcall) {
        Date rocallTime = msgScheduleRollcall.getRocallTime();
        Long rollcallId = new Date().getTime();
        List<RollcallResultEntity> resultList = new ArrayList<>();
        List<RollcallDto> dtoList = getRollcallDtoList();
        for (RollcallDto dto : dtoList) {
            resultList.add(createRollcallResultEntity(rollcallId, new Timestamp(rocallTime.getTime()), dto));
        }
        if (resultList.size() > 0) {
            for (RollcallResultEntity entity : resultList) {
                rollcallResultRepository.save(entity);
            }
        }
    }

    @Override
    public List<RollcallDto> getRollcallDtoList() {
        String sql = "select p.org_id as orgId, o.org_name as orgName, p.id as personnelId, " +
                " p.personnel_name as personnelName, p.card_number as cardNumber, dc.online_status as onlineStatus," +
                " po.id as locationId, po.location_name as locationName, po.bind_org_id as locationBindOrgId, " +
                " c.locator_address as locatorAddress, c.update_time as locateTime " +
                " from personnel p " +
                " left join s_org o on p.org_id = o.id " +
                " left join d_card_active c on p.card_number = c.card_number " +
                " left join d_card dc on c.card_number = dc.card_number " +
                " left join poi_location po on po.map_level = 3 and c.floor_number = po.floor_number and st_contains(po.location_polygon, point(c.x, c.y)) " +
                " where p.org_id is not null and p.is_enabled = 1 and p.personnel_type = 101 " +
                " order by p.org_id";
        List<RollcallDto> list = entityManager.createNativeQuery(sql)
                .unwrap(NativeQuery.class)
                .addScalar("orgId", LongType.INSTANCE)
                .addScalar("orgName", StringType.INSTANCE)
                .addScalar("personnelId", LongType.INSTANCE)
                .addScalar("personnelName", StringType.INSTANCE)
                .addScalar("cardNumber", LongType.INSTANCE)
                .addScalar("onlineStatus", IntegerType.INSTANCE)
                .addScalar("locationId", LongType.INSTANCE)
                .addScalar("locationName", StringType.INSTANCE)
                .addScalar("locationBindOrgId", LongType.INSTANCE)
                .addScalar("locatorAddress", IntegerType.INSTANCE)
                .addScalar("locateTime", TimestampType.INSTANCE)
                .setResultTransformer(Transformers.aliasToBean(RollcallDto.class))
                .list();
        return list;
    }

    @Override
    public List<RollcallDto> getRollcallDtoList(List<Long> orgIdList) {
        if (orgIdList != null && orgIdList.size() > 0) {
            String sql = "select p.org_id as orgId, o.org_name as orgName, p.id as personnelId, " +
                    " p.personnel_name as personnelName, p.card_number as cardNumber, dc.online_status as onlineStatus, " +
                    " po.id as locationId, po.location_name as locationName, po.bind_org_id as locationBindOrgId, " +
                    " c.locator_address as locatorAddress, c.update_time as locateTime " +
                    " from personnel p " +
                    " left join s_org o on p.org_id = o.id " +
                    " left join d_card_active c on p.card_number = c.card_number " +
                    " left join d_card dc on c.card_number = dc.card_number " +
                    " left join poi_location po on po.map_level = 3 and c.floor_number = po.floor_number and st_contains(po.location_polygon, point(c.x, c.y)) " +
                    " where p.is_enabled = 1 and p.personnel_type = 101 and p.org_id IN :idList" +
                    " order by p.org_id";
            List<RollcallDto> list = entityManager.createNativeQuery(sql)
                    .setParameter("idList", orgIdList)
                    .unwrap(NativeQuery.class)
                    .addScalar("orgId", LongType.INSTANCE)
                    .addScalar("orgName", StringType.INSTANCE)
                    .addScalar("personnelId", LongType.INSTANCE)
                    .addScalar("personnelName", StringType.INSTANCE)
                    .addScalar("cardNumber", LongType.INSTANCE)
                    .addScalar("onlineStatus", IntegerType.INSTANCE)
                    .addScalar("locationId", LongType.INSTANCE)
                    .addScalar("locationName", StringType.INSTANCE)
                    .addScalar("locationBindOrgId", LongType.INSTANCE)
                    .addScalar("locatorAddress", IntegerType.INSTANCE)
                    .addScalar("locateTime", TimestampType.INSTANCE)
                    .setResultTransformer(Transformers.aliasToBean(RollcallDto.class))
                    .list();
            return list;
        } else {
            return new ArrayList<>();
        }
    }

    private RollcallResultEntity createRollcallResultEntity(long rollcallId, Timestamp rollcallTime, RollcallDto dto) {
        RollcallResultEntity entity = new RollcallResultEntity();
        entity.setRollcallId(rollcallId);
        entity.setRollcallTime(rollcallTime);

        entity.setOrgId(dto.getOrgId());
        entity.setOrgName(dto.getOrgName());
        entity.setPersonnelId(dto.getPersonnelId());
        entity.setPersonnelName(dto.getPersonnelName());
        entity.setCardNumber(dto.getCardNumber());
        entity.setCardOnlineStatus(dto.getOnlineStatus());
        entity.setLocationId(dto.getLocationId());
        entity.setLocationName(dto.getLocationName());
        entity.setLocationBindOrgId(dto.getLocationBindOrgId());
        entity.setLocatorAddress(dto.getLocatorAddress());
        entity.setLocateTime(dto.getLocateTime());
        entity.setResult(0);    //正常

        if (!entity.getOrgId().equals(entity.getLocationBindOrgId())) {
            //位置异常
            entity.setResult(1);
        } else {
            if (rollcallTime.getTime() - entity.getLocateTime().getTime() > rollcallTimeout) {
                //时间异常
                entity.setResult(2);
            }
        }
        return entity;
    }

    @Override
    public List<RollcallTimeDto> getRollcallTimeList(String dateStr) {
        String sql = "SELECT distinct rollcall_id as rollcallId, rollcall_time as rollcallTime, user_account as userAccount" +
                " FROM rollcall_result " +
                "where rollcall_time >= '" + dateStr + " 00:00:00' and rollcall_time <= '" + dateStr + " 23:59:59'";
        List<RollcallTimeDto> list = entityManager.createNativeQuery(sql)
                .unwrap(NativeQuery.class)
                .addScalar("rollcallId", LongType.INSTANCE)
                .addScalar("rollcallTime", TimestampType.INSTANCE)
                .addScalar("userAccount", StringType.INSTANCE)
                .setResultTransformer(Transformers.aliasToBean(RollcallTimeDto.class))
                .list();
        return list;
    }

    @Override
    public RollcallResultNode getRollcallResult(Long rollcallId) {
        RollcallResultNode rootNode = getRollcallResultRootNode();
        if (rootNode != null) {
            List<RollcallResultEntity> resultList = rollcallResultRepository.getAllByRollcallId(rollcallId);
            if (resultList != null && resultList.size() > 0) {
                for (RollcallResultEntity resultEntity : resultList) {
                    RollcallResultNode node = getNodeByOrgId(rootNode, resultEntity.getOrgId());
                    if (node != null) {
                        node.addRollcallResult(new RollcallResultDto(resultEntity));
                    }
                }
            }
        }
        return rootNode;
    }

    private RollcallResultNode getRollcallResultRootNode() {
        OrgEntity rootOrg = orgRepository.getTopByOrgTypeAndPidIsNull(Constants.ORG_TYPE.PRISONER.id);
        if (rootOrg != null) {
            RollcallResultNode rootNode = new RollcallResultNode(rootOrg.getId(), rootOrg.getOrgName());
            addSubNodes(rootNode);
            return rootNode;
        }
        return null;
    }

    private RollcallResultNode getRollcallResultOrgNode(Long orgId) {
        OrgEntity orgEntity = orgRepository.getById(orgId);
        if (orgEntity != null) {
            RollcallResultNode orgNode = new RollcallResultNode(orgEntity.getId(), orgEntity.getOrgName());
            addSubNodes(orgNode);
            return orgNode;
        }
        return null;
    }

    private void addSubNodes(RollcallResultNode node) {
        List<OrgEntity> subOrgs = orgRepository.getAllByPid(node.getOrgId());
        if (subOrgs != null && subOrgs.size() > 0) {
            for (OrgEntity orgEntity : subOrgs) {
                RollcallResultNode subNode = new RollcallResultNode(orgEntity.getId(), orgEntity.getOrgName());
                node.addChild(subNode);
                addSubNodes(subNode);
            }
        }
    }

    private RollcallResultNode getNodeByOrgId(RollcallResultNode node, Long orgId) {
        if (node.getOrgId().equals(orgId)) {
            return node;
        } else {
            List<RollcallResultNode> children = node.getChildren();
            if (children != null) {
                for (RollcallResultNode sub : children) {
                    RollcallResultNode resultNode = getNodeByOrgId(sub, orgId);
                    if (resultNode != null) {
                        return resultNode;
                    }
                }
            }
            return null;
        }
    }

    @Override
    public Long manualRollcall(String userAccount) {
        Long rollcallId = new Date().getTime();
        List<RollcallTempEntity> resultList = new ArrayList<>();
        List<RollcallDto> dtoList = getRollcallDtoList();
        for (RollcallDto dto : dtoList) {
            resultList.add(createRollcallTempEntity(rollcallId, new Timestamp(rollcallId), userAccount, dto));
        }
        if (resultList.size() > 0) {
            for (RollcallTempEntity entity : resultList) {
                rollcallTempRepository.save(entity);
            }
        }
        return rollcallId;
    }

    private RollcallTempEntity createRollcallTempEntity(long rollcallId, Timestamp rollcallTime,
                                                        String userAccount, RollcallDto dto) {
        RollcallTempEntity entity = new RollcallTempEntity();
        entity.setRollcallId(rollcallId);
        entity.setRollcallTime(rollcallTime);
        entity.setUserAccount(userAccount);
        entity.setOrgId(dto.getOrgId());
        entity.setOrgName(dto.getOrgName());
        entity.setPersonnelId(dto.getPersonnelId());
        entity.setPersonnelName(dto.getPersonnelName());
        entity.setCardNumber(dto.getCardNumber());
        entity.setCardOnlineStatus(dto.getOnlineStatus());
        entity.setLocationId(dto.getLocationId());
        entity.setLocationName(dto.getLocationName());
        entity.setLocationBindOrgId(dto.getLocationBindOrgId());
        entity.setLocatorAddress(dto.getLocatorAddress());
        entity.setLocateTime(dto.getLocateTime());
        entity.setResult(0);    //正常

        if (!entity.getOrgId().equals(entity.getLocationBindOrgId())) {
            //位置异常
            entity.setResult(1);
        } else {
            if (rollcallTime.getTime() - entity.getLocateTime().getTime() > rollcallTimeout) {
                //时间异常
                entity.setResult(2);
            }
        }
        return entity;
    }

    @Override
    public RollcallResultNode getManualRollcallResult(Long rollcallId) {
        RollcallResultNode rootNode = getRollcallResultRootNode();
        if (rootNode != null) {
            List<RollcallTempEntity> resultList = rollcallTempRepository.getAllByRollcallId(rollcallId);
            if (resultList != null && resultList.size() > 0) {
                for (RollcallTempEntity resultEntity : resultList) {
                    RollcallResultNode node = getNodeByOrgId(rootNode, resultEntity.getOrgId());
                    if (node != null) {
                        node.addRollcallResult(new RollcallResultDto(resultEntity));
                    }
                }
            }
        }
        return rootNode;
    }

    @Override
    public Boolean saveManualRollcallResult(Long rollcallId) {
        List<RollcallTempEntity> resultList = rollcallTempRepository.getAllByRollcallId(rollcallId);
        for (RollcallTempEntity tempEntity : resultList) {
            RollcallResultEntity resultEntity =
                    rollcallResultRepository.getByRollcallIdAndPersonnelId(rollcallId, tempEntity.getPersonnelId());
            if (resultEntity == null) {
                resultEntity = new RollcallResultEntity();
            }
            resultEntity.setRollcallId(tempEntity.getRollcallId());
            resultEntity.setRollcallTime(tempEntity.getRollcallTime());
            resultEntity.setUserAccount(tempEntity.getUserAccount());
            resultEntity.setOrgId(tempEntity.getOrgId());
            resultEntity.setOrgName(tempEntity.getOrgName());
            resultEntity.setPersonnelId(tempEntity.getPersonnelId());
            resultEntity.setPersonnelName(tempEntity.getPersonnelName());
            resultEntity.setCardNumber(tempEntity.getCardNumber());
            resultEntity.setCardOnlineStatus(tempEntity.getCardOnlineStatus());
            resultEntity.setLocationId(tempEntity.getLocationId());
            resultEntity.setLocationName(tempEntity.getLocationName());
            resultEntity.setLocationBindOrgId(tempEntity.getLocationBindOrgId());
            resultEntity.setLocatorAddress(tempEntity.getLocatorAddress());
            resultEntity.setLocateTime(tempEntity.getLocateTime());
            resultEntity.setResult(tempEntity.getResult());
            resultEntity.setRemark(tempEntity.getRemark());

            rollcallResultRepository.save(resultEntity);
        }
        return true;
    }

    @Override
    public RollcallTempEntity updateRollcallRecord(Long resultId, RollcallTempEntity entity) throws AppException {
        RollcallTempEntity tempEntity = rollcallTempRepository.getById(resultId);
        if (tempEntity == null) {
            throw new AppException(Response.Status.NOT_FOUND.getStatusCode(),
                    CustomErrors.resource_not_found.getCode(), CustomErrors.resource_not_found.getReason(), "");
        } else {
            tempEntity.setResult(entity.getResult());
            tempEntity.setRemark(entity.getRemark());
            return rollcallTempRepository.save(tempEntity);
        }
    }

    @Override
    public Long getManualRollcallId() {
        return new Date().getTime();
    }

    @Override
    public RollcallResultNode manualRollcall(Long rollcallId, Long orgId, String userAccount) {
        List<Long> orgIdList = orgService.getSubOrgIdList(orgId);
        //点名临时记录保存到数据库
        List<RollcallTempEntity> resultList = new ArrayList<>();
        List<RollcallDto> dtoList = getRollcallDtoList(orgIdList);
        for (RollcallDto dto : dtoList) {
            resultList.add(createRollcallTempEntity(rollcallId, new Timestamp(rollcallId), userAccount, dto));
        }
        if (resultList.size() > 0) {
            for (RollcallTempEntity entity : resultList) {
                rollcallTempRepository.save(entity);
            }
        }
        //返回RollcallResultNode
        RollcallResultNode orgNode = getRollcallResultOrgNode(orgId);
        if (orgNode != null) {
            if (resultList.size() > 0) {
                for (RollcallTempEntity resultEntity : resultList) {
                    RollcallResultNode node = getNodeByOrgId(orgNode, resultEntity.getOrgId());
                    if (node != null) {
                        node.addRollcallResult(new RollcallResultDto(resultEntity));
                    }
                }
            }
        }
        return orgNode;
    }
}
